package framework.eea.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import framework.eea.exception.EEAException;
import framework.eea.interfaces.Game;

/**
 *
 * @author Markus Schr&ouml;der
 */
public class JFrameGameContainer implements Runnable {

    private Thread thread;
    
    private JFrame frame;
    protected Game game;
    private Point location;
    protected Dimension screenSize;
    private volatile boolean closed;
    
    private Graphics graphics;
    
    private Timer renderTimer;
    
    private boolean printout = true;
    private int tab = 0;
    private final int TAB_WIDTH = 4;
    
    public static final float FPS = 60;
    
    private Map<String, Image> images;

    public JFrameGameContainer(Game game, Dimension screenSize) {
        this.game = game;
        this.screenSize = screenSize;
        images = new HashMap<String, Image>();
        this.graphics = new Graphics(this);
        this.location = null;
    }
    
    public JFrameGameContainer(Game game, Point location, Dimension screenSize) {
        this(game, screenSize);
        this.location = location;
    }
    
    
    // START
    
    public void startBlocking() throws EEAException {
        setup();
        updateLoop();
        setdown();
    }
    
    public void startNonBlocking() {
        thread = new Thread(this);
        thread.start();
    }
    
    @Override
    public void run() {
        try {
            setup();
            updateLoop();
            setdown();
        } catch (EEAException ex) {
            errorAlways("run", ex);
            System.exit(1);
        }
    }

    protected void setup() throws EEAException {
        game.init(this);
        setupJFrame();
        setupRenderTimer();
    }
    
    protected void setupJFrame() {
        
        final JFrameGameContainer jfgc = this;
        
        //show frame in EDT
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                
                //panel to render
                JPanel p = new JPanel() {
                    @Override
                    public void paint(java.awt.Graphics g) {
                        super.paint(g);
                        render(g);
                    }
                };
                p.setBackground(Color.black);
                p.setDoubleBuffered(true);
                
                //main frame
                frame = new JFrame(game.getTitle());
                
                //frame that has the panel
                getFrame().setSize(screenSize);
                getFrame().setPreferredSize(screenSize);
                getFrame().setResizable(false);
                if(getLocation() == null)
                    getFrame().setLocationRelativeTo(null);
                else
                    getFrame().setLocation(getLocation());
                
                getFrame().setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                getFrame().setContentPane(p);
                getFrame().addWindowListener(new DefaultWindowListener() {
                    @Override
                    public void windowClosed(WindowEvent we) {
                        closed = true;
                    }
                });
                
                getFrame().addKeyListener(game);
                getFrame().addMouseListener(game);
                getFrame().addMouseMotionListener(game);
                
                getFrame().setUndecorated(true);
                
                getFrame().setVisible(true);
            }
        });
        closed = false;
    }
    
    protected void setupRenderTimer() {
        //render with fps
        renderTimer = new Timer((int)(1000 / FPS), new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                if(getFrame() != null)
                    getFrame().getContentPane().repaint();
            }
        });
        renderTimer.start();
    }
    
    protected void updateLoop() {
        
        //updater
        int wait = 0;
        long begin;
        while(!closed) {
            begin = System.currentTimeMillis();
            try {
                game.update(this, wait);
            } catch (EEAException ex) {
                errorAlways("game.update", ex);
            }
            
            try { Thread.sleep(10); } catch (InterruptedException ex) {}
            
            wait = (int) (System.currentTimeMillis() - begin);
        }
        
    }

    protected void setdown() throws EEAException {
        getFrame().setVisible(false);
        getFrame().dispose();
        
        renderTimer.stop();
        
        game.deinit(this);
    }
    
    private void render(java.awt.Graphics g) {
        graphics.update(g);
        try {
            game.render(this, graphics);
        } catch (EEAException ex) {
            errorAlways("game.render", ex);
        }
    }
    
    public void stop() {
        getFrame().setVisible(false);
        getFrame().dispose();
    }
    
    
    // RESOURCE
    
    public void loadImage(String ref) throws IOException {
        images.put(ref, ImageIO.read(JFrameGameContainer.class.getResource(ref)));
    }
    
    public Image getImage(String ref) {
        return images.get(ref);
    }
    
    public Set<Entry<String, Image>> getAllImages() {
        return images.entrySet();
    }
    
    //LOG
    
    public void ln() {
        if (!isPrintout()) {
            return;
        }

        System.out.println();
    }

    public void info(Object message) {
        if (!isPrintout()) {
            return;
        }

        if (message instanceof String && ((String) message).contains("\n")) {
            int linenumber = 1;

            for (String line : ((String) message).split("\n")) {
                logln(System.out, "INFO", getTabs() + String.format("%02d| ", linenumber) + line, null);
                linenumber++;
            }

        } else {
            logln(System.out, "INFO", getTabs() + message, null);
        }
    }

    public void collection(Collection collection) {
        logln(System.out, "INFO", "Collection:", null);
        for (Object obj : collection) {
            System.out.println("\t" + obj);
        }
    }

    public void error(Object message, Throwable throwable) {
        if (!isPrintout()) {
            return;
        }

        logln(System.err, "ERROR", message, throwable);
    }

    public void infoAlways(Object message) {
        logln(System.out, "INFO", message, null);
    }

    public void errorAlways(Object message, Throwable throwable) {
        logln(System.err, "ERROR", message, throwable);
    }

    private void logln(PrintStream stream, String name, Object message, Throwable throwable) {
        stream.println("[" + name + " " + now() + "] " + message);
        if (throwable != null) {
            throwable.printStackTrace(stream);
        }
    }

    private String getTabs() {
        String str = "";
        for (int i = 0; i < tab; i++) {
            str += "\t";
        }
        return str;
    }

    private String now() {
        SimpleDateFormat formater = new SimpleDateFormat();
        //formater.applyPattern("yyyy-MM-dd HH:mm:ss");
        formater.applyPattern("HH:mm:ss.SSS");
        return formater.format(new Date());
    }

    /**
     * @return the printout
     */
    public boolean isPrintout() {
        return printout;
    }

    /**
     * @param printout the printout to set
     */
    public void setPrintout(boolean printout) {
        this.printout = printout;
    }

    public void incTab() {
        tab += TAB_WIDTH;
    }

    public void decTab() {
        tab -= TAB_WIDTH;
        if (tab < 0) {
            tab = 0;
        }
    }

    /**
     * @return the frame
     */
    public JFrame getFrame() {
        return frame;
    }

    /**
     * @return the location
     */
    public Point getLocation() {
        return location;
    }

    /**
     * @param location the location to set
     */
    public void setLocation(Point location) {
        this.location = location;
    }

    
   

    
}
